var previewController = new function(){
	
	var   _$objectList
		, _$eventList
		, _$stateList
		, _objectDataSource
		, _eventDataSource
		, _stateDataSource
		, _$playBtn
		, _$openPreview
		, _$restartPreview
		, _$container
		, _$exitBtn
		, _$btnContainer
		, _$canvasOverlay
		, _$errorMsg
	;
	
	var loopEnd = false;
	var loopDuration = 0;
	var isActionToBo = false;
	
	//repeat sequence variables
	var repeatSequence = 0
	  , leaf2repeat
	  , actions2repeat
	;
	
	this.bookAudio;
	
	/*
	 * Array que almacena todos los movimientos realizados
	 * Formato: [imageObj, movimiento, parámetro, removeObject]
	 * 
	 */
	var _previewBack = [];
		
	
	function _init(){
		
		_$container = $('#preview');
		_$playBtn = $("#preview-play-btn");
		_$openPreview = $('#open-preview');
		_$restartPreview = $('#restart-preview');
		_$exitBtn = $('#exit-preview');
		_$btnContainer = $('#preview-message-wrapper');
		_$canvasOverlay =$('#canvas-overlay');
		_$errorMsg = $('#preview-message');
		
		/*
		 * Combos de selección de acción
		 */
		_$objectList = $('#object-list');
		_$eventList = $('#event-list');
		_$stateList = $('#state-list');
		
		_objectDataSource = new kendo.data.DataSource({data: leftTree.getObjects()});
		_stateDataSource = new kendo.data.DataSource({data: stateController.getStates()});
		_eventDataSource = new kendo.data.DataSource({
			data: [
			       {id: "touch", name: "touch"},
			       {id: "onEnter", name: "onEnter"},
			       {id: "shakeStart", name: "shakeStart"},
			       {id: "shakeStop", name: "shakeStop"},
			       {id: "dragBegin", name: "dragBegin"},
			       {id: "dragging", name: "dragging"},
			       {id: "drop", name: "drop"},     
			]
		});

		_$objectList 
			.kendoDropDownList({
				dataTextField: "name",
	            dataValueField: "id",
	            dataSource: _objectDataSource,
	            change: _onChange
			})
			.data("kendoDropDownList");
		
		_$eventList 
			.kendoDropDownList({
				dataTextField: "name",
	            dataValueField: "id",
	            dataSource: _eventDataSource,
	            change: _onChange
			})
			.data("kendoDropDownList");
		
		_$stateList 
			.kendoDropDownList({
				dataTextField: "name",
	            dataValueField: "id",
	            dataSource: _stateDataSource,
	            change: _onChange
			})
			.data("kendoDropDownList");
		
		
		/*
		 *  Funcionalidad de los botones
		 */
		_$playBtn.on('click', function(){
			if (!$(this).hasClass('off')){
				play();	
			}
		})
		
		_$openPreview.on('click', function(){
			_open();
		})
		
		_$restartPreview.on('click', function(){
			if (!$(this).hasClass('off')){
				_restart();
				_$playBtn.removeClass('off');
				_$exitBtn.removeClass('off');
			}
		});
		
		_$exitBtn.on('click', function(){
			if (!$(this).hasClass('off')){
				_close();
			}
		})
		
		previewController.bookAudio = document.createElement('audio');
		previewController.initAudio = null;
		previewController.actionAudios = [];
		
	}//end _init function
	
	
	function _onChange(){

		if (!_$restartPreview.hasClass('off')){
			_restart()
			_$playBtn.removeClass('off');
			_$exitBtn.removeClass('off');
		}
	}
	
	
	//actualizar los dataSource de cada combo
	function _update(){
		
		_objectDataSource.data(leftTree.getObjects())
		_$objectList.data("kendoDropDownList").refresh();
		
		_stateDataSource.data(stateController.getStates())
		_$stateList.data("kendoDropDownList").refresh();
		
		//reseteo de los combos
		_$objectList.data("kendoDropDownList").select(0);
		_$eventList.data("kendoDropDownList").select(0);
		_$stateList.data("kendoDropDownList").select(0);
	}
	
	
	//mostrar previsualización
	function _open(){
		
		//Ancho del contenedor en función del leftPanel
		_$container.width( $('#leftPanel').outerWidth() - parseInt(_$container.css('padding-left'))*2 );
		
		//Posición de la botonera en función del mainCanvasWrapper
		_$btnContainer.css('top', $('.mainCanvasWrapper').outerHeight() + $('.mainCanvasWrapper').offset().top );
		_$errorMsg.width( $('#middlePanel').outerWidth() - parseInt(_$errorMsg.css('padding-left'))*2); 
		_$errorMsg.height( $('#sceneListWrapper').outerHeight() - 40);
		
		//Canvas overlay
		_$canvasOverlay.css({
			top:  $('.mainCanvasWrapper').offset().top,
			left: $('.mainCanvasWrapper').offset().left,
			width: $('.mainCanvasWrapper').width(),
			height: $('.mainCanvasWrapper').height()
		});
		
		//Desactivar objeto seleccionado
		sliderController.disableAll(); 
		mainCanvas.canvas.deactivateAll();
		mainCanvas.canvas.renderAll();
		leftTree.unselectAllNodes();
		
		//Esconder boón timeline
		timeline.Hide();
		
		_update();
		
		//Para los bucles infinitos
		loopEnd = false;
		
		$('header').fadeOut();
		$('#leftPanel').fadeOut();
		$('#sceneListWrapper').fadeOut();
		$('#rightPanel').fadeOut();
		$('.k-splitbar').fadeOut();
		
		_$canvasOverlay.show();
		_$container.fadeIn();
		_$btnContainer.fadeIn();
		_$errorMsg.html('');
		_$restartPreview.addClass('off');
		_$exitBtn.removeClass('off');
		_$playBtn.removeClass('off');
	}
	
	
	//cerrar previsualización
	function _close(){
		
		_restart();
		
		_$canvasOverlay.hide();
		_$container.hide();
		_$btnContainer.hide();

		
		$('header').show();
		$('#leftPanel').show();
		$('#sceneListWrapper').show();
		$('#rightPanel').show();
		$('.k-splitbar').show();	
		
		timeline.ReOpen();
	}
	
	
	//ejecutar movimiento
	function play(){
		
		var   objectID = _$objectList.data("kendoDropDownList").value()
			, objectName = _$objectList.data("kendoDropDownList").text()
			, event = _$eventList.data("kendoDropDownList").text()
			, state = _$stateList.data("kendoDropDownList").text()
			, leaf = leftTree.treeView.dataSource.getByUid(objectID)
			, sequence
		;
		
		if (objectID){
			
			sequence = getSequenceToPlay(leaf, event, state);
			
			if (sequence){
				
				_$errorMsg.html('');
				_$playBtn.addClass('off');
				_$restartPreview.addClass('off');
				_$exitBtn.addClass('off');
				
				playSequence(leaf, sequence);
			}
			else {
				
				_$errorMsg.append($('<p>').addClass('error').html('El objeto <i>' + objectName + '</i> en el estado <i>' + state + '</i> no tiene acciones asociadas al evento <i>' + event + '</i>'));
			}
		}
		else {
			
			_$errorMsg.append($('<p>').addClass('error').html('No hay ningún objeto seleccionado.'));
		}
		
		
	}
	
	
	//Buscar en el nodo del árbol, que secuencia hay que ejecutar
	function getSequenceToPlay(leaf, event, state){
		
		var r = false;
		
		if (leaf.sequences){
			$.each(leaf.sequences, function(i,seq){
				
				if (seq.actions && seq.actions.length && seq.actions[0].action.event == event && seq.actions[0].action.state == state){
					
					r = leaf.sequences[i];
				}
			});	
		}
		
		return r;
	}
	
	function playRepeatSequence(){
			
		repeatSequence--;
		_restart(function(){
			
			playAction(leaf2repeat, actions2repeat, 0);
		});
		
	}
	
	
	//ejecutar la sequencia que coincide con par evento-estado seleccionados
	function playSequence(leaf, sequence){
		
		loopEnd = false;
		
		repeatSequence = sequence.repeat;
		leaf2repeat = leaf;
		actions2repeat = sequence.actions;
		
		//ejecutar la primera acción de la secuencia. 
		//Las demás se ejecutan desde el callback de la anterior
		playAction(leaf, sequence.actions, 0);
		
	}
	
	
	//ejecutar una acción
	function playAction(leaf, seqActions, index){
		
		var isSpawn = (typeof index === "boolean" && index == false);
		
		if (isSpawn){
			var action = seqActions;
		}
		else {
			var action = (seqActions[index]) ? seqActions[index].action : false;	
		}
		
		
		if (action){

			var type = action.type;
			var repeat = action.repeat;
			
			//tipos de acciones con duración
			if (type == 'DDMoveTo' || type == 'DDMoveBy' || type == 'DDRotateTo' || type == 'DDRotateBy' || type == 'DDScaleTo' ||
				type == 'DDScaleBy' || type == 'DDFadeIn' || type == 'DDFadeOut' || type == 'DDFrameAction' || type == "DDDelay"){
				
				//duración en milisegundos
				var duration = action.duration * 1000 || 0;
				var durationString = (action.duration == 1) ? " segundo..." : " segundos...";
				
				_$errorMsg.append($('<p>').html('Ejecutando la acción <i>' + action.type + '</i> sobre el objeto <i>' + leaf.text + '</i> durante ' + action.duration + durationString));	
			}
			else {
				
				_$errorMsg.append($('<p>').html('Ejecutada la acción <i>' + action.type + '</i> sobre el objeto <i>' + leaf.text + '</i>'));
			}
			
			
			switch(type){
				case "DDMoveTo": playDDMoveTo(leaf, seqActions, index, action, duration, repeat); break;
				case "DDMoveBy": playDDMoveBy(leaf,seqActions, index, action, duration, repeat); break;
				case "DDRotateTo": playDDRotateTo(leaf, seqActions, index, action, duration, repeat); break;
				case "DDRotateBy": playDDRotateBy(leaf, seqActions, index, action, duration, repeat); break;
				case "DDScaleTo": playDDScaleTo(leaf, seqActions, index, action, duration, repeat); break;
				case "DDScaleBy": playDDScaleBy(leaf, seqActions, index, action, duration, repeat); break;
				case "DDFadeIn": playDDFadeIn(leaf, seqActions, index, action, duration, repeat); break;
				case "DDFadeOut": playDDFadeOut(leaf, seqActions, index, action, duration, repeat); break;
				case "DDVibrate": playDDVibrate(leaf, seqActions, index, action, duration, repeat); break;
				case "DDFrameAction": playDDFrameAction(leaf, seqActions, index, duration, action); break;
				case "DDTextureChange": playDDTextureChange(leaf, seqActions, index, action); break;
				case "DDPlayActionToBo": playDDPlayActionToBo(leaf, seqActions, index, action); break;
				case "DDEnableBo": playDDEnableBo(leaf, seqActions, index, action); break;
				case "DDLaunchScene": playDDLaunchScene(leaf, seqActions, index, action); break;
				case "DDLaunchNextScene": playDDLaunchNextScene(leaf, seqActions, index); break;
				case "DDLaunchPrevScene": playDDLaunchPrevScene(leaf, seqActions, index); break;
				case "DDEnableBGMusic": playDDEnableBGMusic(leaf, seqActions, index, action); break;
				case "DDChangeBGMusic": playDDChangeBGMusic(leaf, seqActions, index, action); break;
				case "DDStopAction": playDDStopAction(leaf, seqActions, index, action); break;
				case "DDDropEnd": playDDDropEnd(leaf, seqActions, index); break;
				case "DDHighlight": playDDHighlight(leaf, seqActions, index); break;
				case "DDIndexChange": playDDIndexChange(leaf, seqActions, index, action); break;
				case "DDStateChange": playDDStateChange(leaf, seqActions, index, action); break;
				case "DDPlayCustomAction": playDDPlayCustomAction(leaf, seqActions, index, action); break;
				case "DDDelay": playDDDelay(leaf, seqActions, index, action, duration); break;
				case "DDSpawn": playDDSpawn(leaf,seqActions, index, action); break;
					
				default: console.log('Ninguna animación asociada al tipo ' + type); break;
			}
		}
		else if (!isSpawn) {
			
			
			if (repeatSequence > 0){
		
				playRepeatSequence();
			}
			else if (repeatSequence == -1){
				
				//_restart();
				
				_$errorMsg.append($('<p>').html(' -- La secuencia se repite indefinidamente... -- '));
				
				//_$playBtn.removeClass('off');
				if (_previewBack.length){
					_$restartPreview.removeClass('off');
				}
				_$exitBtn.removeClass('off');
			}
			else {
				
				_$errorMsg.append($('<p>').html(' -- Fin de la ejecución de la secuencia -- '));
				
				//_$playBtn.removeClass('off');
				if (_previewBack.length){
					_$restartPreview.removeClass('off');
				}
				_$exitBtn.removeClass('off');
			}

		}
	}
	
	
	function loop(go, reset, leaf, seqActions, index, repeat, duration, loopFlag, audio){
		
		//bucle infinito
		if (repeat == -1){
			
			//fin del bucle
			if (loopEnd){
				
				return;
			}
			
			if (!loopFlag){
				loopFlag = true;
				loopDuration = duration;
				_$errorMsg.append($('<p>').html('El objeto ' + leaf.text + ' entra en un bucle infinito...'));
				_$restartPreview.removeClass('off');
				_$exitBtn.removeClass('off');
				_$playBtn.removeClass('off');
			}
			reset();
			go(repeat, loopFlag);
		}

		//repetir acción
		else if (repeat > 0){
			repeat--;
			reset();
			go(repeat, loopFlag);
		}
		
		//siguiente acción
		else {
			
			if (!isActionToBo){
				if (!(typeof index === "boolean")){
					playAction(leaf, seqActions, ++index)
				}
			}
			else {
				isActionToBo = false;
			}
			
		}
	}
	
	
	function playMusic(source, duration, repeat){
		
		var PATH = '/getFile?fileName=';
		
		if (source){
			var audioObj = new Audio();
			audioObj.src = PATH + source;
			previewController.actionAudios.push(audioObj);
			audioObj.play();
			
			var timer = (repeat > 0) ? duration*repeat : duration;
			
			if (repeat != -1){
				window.setTimeout(
					function(){
						stopMusic(audioObj);
					}
					, timer
				)
			}
			
		}
	}
	
	
	function stopMusic(audio){
		
		if (audio){
			audio.pause();
			delete audio;	
		}
	}
	
	
	/*
	 * FUNCIONES ASOCIADAS A CADA TIPO DE MOVIMIENTO
	 */
	
	//DDSpawn
	function playDDSpawn(leaf,seqActions, index, action){
		
		var duration = 0;
		
		if (action.cfg1){
			
			$.each(action.cfg1, function(i, e){
				
				var d = (action.cfg1[i].duration) ?  action.cfg1[i].duration * 1000 : 0;
				if (action.cfg1[i].repeat > 0){
					d = d * action.cfg1[i].repeat;
				}
				
				if (d > duration){
					duration = d;
				}
				
				//TODO ejecutar accion
				window.console.log(action.cfg1[i].name)
				playAction(leaf, action.cfg1[i], false);
			})
		}
		
		window.setTimeout(
			function(){
			
				window.console.log('Siguiente accion tras spawn despues de ' + duration + ' milisegundos.')
				//siguiente acción
				playAction(leaf, seqActions, ++index);
			}
			, duration
		);
		
	}
	
	
	
	//DDMoveTo
	function playDDMoveTo(leaf, seqActions, index, action, duration, repeat){
		
		var d = duration || 0.01;
		var loopFlag = false;		
		
		var iniLeft = leaf.imageObj.get('left');
		var iniTop = leaf.imageObj.get('top');
		var finalLeft = action.cfg1 * deviceController.canvasRatio;
		var finalTop = action.cfg11 * deviceController.canvasRatio;	
		
		//Almacenar posición inicial
		previewBackPush(leaf.imageObj, 'left', iniLeft);
		previewBackPush(leaf.imageObj, 'top', iniTop);
		
		function go(p_repeat, p_loopFlag){
			leaf.imageObj.animate('left', finalLeft, {
				duration: d 
			});
			leaf.imageObj.animate('top', finalTop, {
				duration: d,
				onChange: mainCanvas.canvas.renderAll.bind(mainCanvas.canvas),
				onComplete: function(){
					mainCanvas.canvas.renderAll.bind(mainCanvas.canvas); 
					loop(go, reset, leaf, seqActions, index, p_repeat, d, p_loopFlag);
				}
			});
		}
	
		function reset(){
			leaf.imageObj.set('left', iniLeft);
			leaf.imageObj.set('top', iniTop);
			mainCanvas.canvas.renderAll();
		}
		
		if (action.fx) {
			playMusic(action.fx, duration, repeat);
		}
		
		go(repeat, loopFlag);

	}
	
	//DDMoveBy
	function playDDMoveBy(leaf, seqActions, index, action, duration, repeat){
		
		var d = duration || 0.01;
		var loopFlag = false;
		var audio = null;
		
		var iniLeft = parseFloat(leaf.imageObj.get('left'));
		var iniTop = parseFloat(leaf.imageObj.get('top'));
		var finalLeft = parseFloat(action.cfg1);
		var finalTop = parseFloat(action.cfg11);
		
		//Almacenar posición inicial
		previewBackPush(leaf.imageObj, 'left', iniLeft);
		previewBackPush(leaf.imageObj, 'top', iniTop);
		
		function go(p_repeat, p_loopFlag){
			
			leaf.imageObj.animate('left', iniLeft + finalLeft, {
				duration: d
			});
			
			leaf.imageObj.animate('top', iniTop + finalTop, {
				duration: d,
				onChange: mainCanvas.canvas.renderAll.bind(mainCanvas.canvas),
				onComplete: function(){
					mainCanvas.canvas.renderAll.bind(mainCanvas.canvas); 
					loop(go, reset, leaf, seqActions, index, p_repeat, d, p_loopFlag);
				}
			});
		}
		
		function reset(){
			leaf.imageObj.set('left', iniLeft);
			leaf.imageObj.set('top', iniTop);
			mainCanvas.canvas.renderAll();
		}
		
		if (action.fx) {
			playMusic(action.fx);
		}
		
		go(repeat, loopFlag);
	}
	
	//DDRotateTo
	function playDDRotateTo(leaf, seqActions, index, action, duration, repeat){
		
		var d = duration || 0.01;
		var loopFlag = false;
		var audio = null;

		var iniPos = leaf.imageObj.get('angle');
		var finalPos = action.cfg1;
		
		//Almacenar posición inicial
		previewBackPush(leaf.imageObj, 'angle', iniPos);
		
		function go(p_repeat, p_loopFlag){
			
			leaf.imageObj.animate('angle', finalPos, {
				duration: d,
				easing: function(t, b, c, d) { return c*t/d + b; },
				onChange: mainCanvas.canvas.renderAll.bind(mainCanvas.canvas),
				onComplete: function(){
					mainCanvas.canvas.renderAll.bind(mainCanvas.canvas); 
					loop(go, reset, leaf, seqActions, index, p_repeat, d, p_loopFlag);
				}
			});
		}
		
		function reset(){
			leaf.imageObj.set('angle', iniPos);
			mainCanvas.canvas.renderAll();
		}
		
		if (action.fx) {
			playMusic(action.fx);
		}
		
		go(repeat, loopFlag);
	}
	
	//DDRotateBy
	function playDDRotateBy(leaf, seqActions, index, action, duration, repeat){
		
		var d = duration || 0.01;
		var loopFlag = false;
		var audio = null;

		var iniPos = leaf.imageObj.get('angle');
		var finalPos = parseFloat(action.cfg1);
		
		//Almacenar posición inicial
		previewBackPush(leaf.imageObj, 'angle', iniPos);
		
		function go(p_repeat, p_loopFlag){
		
			leaf.imageObj.animate('angle', iniPos+finalPos, {
				duration: d,
				easing: function(t, b, c, d) { return c*t/d + b; },
				onChange: mainCanvas.canvas.renderAll.bind(mainCanvas.canvas),
				onComplete: function(){
					mainCanvas.canvas.renderAll.bind(mainCanvas.canvas); 
					loop(go, reset, leaf, seqActions, index, p_repeat, d, p_loopFlag);
				}
			});
		}
		
		function reset(){
			leaf.imageObj.set('angle', iniPos);
			mainCanvas.canvas.renderAll();
		}
		
		if (action.fx) {
			playMusic(action.fx);
		}
		
		go(repeat, loopFlag);
	}
	
	//DDScaleTo
	function playDDScaleTo(leaf, seqActions, index, action, duration, repeat){
		
		var d = duration || 0.01;
		var loopFlag = false;
		var audio = null;
		
		var iniScaleX = leaf.imageObj.get('scaleX');
		var iniScaleY = leaf.imageObj.get('scaleY');
		var finalScaleX = action.cfg1;
		var finalScaleY = action.cfg11;
		
		//Almacenar posición inicial
		previewBackPush(leaf.imageObj, 'scaleX', iniScaleX);
		previewBackPush(leaf.imageObj, 'scaleY', iniScaleY);
		
		function go(p_repeat, p_loopFlag){
			
			//fabricjs bug workaround
			//Seteo previo a la animación 
			leaf.imageObj.set('scaleX', parseFloat(iniScaleX));
			leaf.imageObj.set('scaleY', parseFloat(iniScaleY));
	
			leaf.imageObj.animate('scaleX', parseFloat(finalScaleX), {
				duration: d,
			});
			
			leaf.imageObj.animate('scaleY', parseFloat(finalScaleY), {
				duration: d,
				onChange: mainCanvas.canvas.renderAll.bind(mainCanvas.canvas),
				onComplete: function(){
					mainCanvas.canvas.renderAll.bind(mainCanvas.canvas); 
					loop(go, reset, leaf, seqActions, index, p_repeat, d, p_loopFlag);
				}
			});
		}
		
		function reset(){
			leaf.imageObj.set('scaleX', parseFloat(iniScaleX));
			leaf.imageObj.set('scaleY', parseFloat(iniScaleY));
			mainCanvas.canvas.renderAll();
		}
		
		if (action.fx) {
			playMusic(action.fx);
		}
		
		go(repeat, loopFlag);
	}
	
	
	//DDScaleBy
	function playDDScaleBy(leaf, seqActions, index, action, duration, repeat){
		
		var d = duration || 0.01;
		var loopFlag = false;
		var audio = null;
		
		var iniScaleX = leaf.imageObj.get('scaleX');
		var iniScaleY = leaf.imageObj.get('scaleY');
		var finalScaleX = parseFloat(action.cfg1);
		var finalScaleY = parseFloat(action.cfg11);
		
		//Almacenar posición inicial
		previewBackPush(leaf.imageObj, 'scaleX', iniScaleX);
		previewBackPush(leaf.imageObj, 'scaleY', iniScaleY);
		
		function go(p_repeat, p_loopFlag){
			
			leaf.imageObj.animate('scaleX', iniScaleX + finalScaleX, {
				duration: d,
			});
			
			leaf.imageObj.animate('scaleY', iniScaleY + finalScaleY, {
				duration: d,
				onChange: mainCanvas.canvas.renderAll.bind(mainCanvas.canvas),
				onComplete: function(){
					mainCanvas.canvas.renderAll.bind(mainCanvas.canvas); 
					loop(go, reset, leaf, seqActions, index, p_repeat, d, p_loopFlag);
				}
			});
		}
		
		function reset(){
			leaf.imageObj.set('scaleX', iniScaleX);
			leaf.imageObj.set('scaleY', iniScaleY);
			mainCanvas.canvas.renderAll();
		}
		
		if (action.fx) {
			playMusic(action.fx);
		}
		
		go(repeat, loopFlag);
	}
	
	
	//DDFadeIn
	function playDDFadeIn(leaf, seqActions, index, action, duration, repeat){
		
		var d = duration || 0.01;
		var loopFlag = false;
		var audio = null;
		
		var iniPos = leaf.imageObj.get('opacity');
		
		//Almacenar posición inicial
		previewBackPush(leaf.imageObj, 'opacity', iniPos);
		
		function go(p_repeat, p_loopFlag){
		
			//fabricjs bug workaround
			//Seteo previo a la animación 
			leaf.imageObj.set('opacity', parseFloat(iniPos));
			
			leaf.imageObj.animate('opacity', 1, {
				duration: d,
				onChange: mainCanvas.canvas.renderAll.bind(mainCanvas.canvas),
				onComplete: function(){
					mainCanvas.canvas.renderAll.bind(mainCanvas.canvas); 
					loop(go, reset, leaf, seqActions, index, p_repeat, d, p_loopFlag);
				}
			});
		}
		
		function reset(){
			leaf.imageObj.set('opacity', iniPos);
			mainCanvas.canvas.renderAll();
		}
		
		if (action.fx) {
			playMusic(action.fx);
		}
		
		go(repeat, loopFlag);
	}
	
	
	//DDFadeOut
	function playDDFadeOut(leaf, seqActions, index, action, duration, repeat){
		
		var d = duration || 0.01;
		var loopFlag = false;
		var audio = null;
		
		var iniPos = leaf.imageObj.get('opacity');
		
		//Almacenar posición inicial
		previewBackPush(leaf.imageObj, 'opacity', iniPos);
		
		function go(p_repeat, p_loopFlag){
			
			//fabricjs bug workaround
			//Seteo previo a la animación 
			leaf.imageObj.set('opacity', parseFloat(iniPos));
			
			leaf.imageObj.animate('opacity', 0, {
				duration: d,
				onChange: mainCanvas.canvas.renderAll.bind(mainCanvas.canvas),
				onComplete: function(){
					mainCanvas.canvas.renderAll.bind(mainCanvas.canvas); 
					loop(go, reset, leaf, seqActions, index, p_repeat, d, p_loopFlag);
				}
			});	
		}
		
		function reset(){
			leaf.imageObj.set('opacity', iniPos);
			mainCanvas.canvas.renderAll();
		}
		
		if (action.fx) {
			playMusic(action.fx);
		}
		
		go(repeat, loopFlag);
	}
	
	
	//playDDVibrate
	function playDDVibrate(leaf, seqActions, index, action, duration, repeat){
		
		//siguiente acción
		playAction(leaf, seqActions, ++index);
	}
	
	
	//playDDFrameAction
	function playDDFrameAction(leaf, seqActions, index, duration, action){
		
		var   d = duration || 0.01
			, exit = false
			, index = 1
			, audio = null
		;
		
		while (!exit){
			
			if (action.hasOwnProperty('cfg' + index)){
				
				_$errorMsg.append($('<p>').html('DDFrameAction ejecuta el frame <i>' + action['cfg'+index] + '</i>'));
				++index;
			}
			else {
				exit = true;
			}
		}
		
		if (action.fx) {
			playMusic(action.fx);
		}
		
		window.setTimeout(function(){
			
			stopMusic(audio);
			playAction(leaf, seqActions, ++index);	
		}, d);
		
	}
	
	
	//DDTextureChange
	function playDDTextureChange(leaf, seqActions, index, action){
		
		var FILE_SRC = "/getFile?fileName=";
		
		var   type = action.type
			, ini = leaf.imageObj.getElement().src
			, img = action.cfg1
			, finalImg = FILE_SRC + img
		;
		
		$.get(finalImg)
			.error(function(data){
				
				_$errorMsg.append($('<p>').addClass('error').html('Error en la ejecución de <i>' + type + '</i>: no se encuentra la imagen <i>' + img + '</i>'));
				
				//siguiente acción
				playAction(leaf, seqActions, ++index)  
			})
			.success(function(data){
				
				if (data){

					//Almacenar estado inicial
					previewBackPush(leaf.imageObj, 'src', ini);
						
					$(leaf.imageObj.getElement()).load(function(){
						mainCanvas.canvas.renderAll();
					});
					leaf.imageObj.getElement().setAttribute('src', finalImg);
					mainCanvas.canvas.renderAll();

					//siguiente acción
					playAction(leaf, seqActions, ++index) 
					

				}
				else {
					
					_$errorMsg.append($('<p>').addClass('error').html('Error en la ejecución de <i>' + type + '</i>: no se encuentra la imagen <i>' + img + '</i>'));
					
					//siguiente acción
					playAction(leaf, seqActions, ++index)  
				}
				
				
				
			})//end success 
	}
	
	
	//DDPlayActionToBo
	function playDDPlayActionToBo(leaf, seqActions, index, action){
		
		var obj = action.cfg1
		  , act = action.cfg11
		  , d
		;
		
		//Flag para ejecutar una única acción del objeto destino
		isActionToBo = true;
		
		var toExec = leftTree.getActionByName(obj, act);
		
		if (toExec){
			playAction(toExec.obj, toExec.seq, toExec.index);
		}
		else {
			_$errorMsg.append($('<p>').addClass('error').html('Imposible ejecutar <i>' + act + '</i> sobre <i>' + obj + '</i>.'));
		}
		
		d = (toExec) ? toExec.duration*1000 : 0.01;
		
		window.setTimeout(function(){
			playAction(leaf, seqActions, ++index);	
		}, d);
	}
	
	
	//DDEnableBo
	function playDDEnableBo(leaf, seqActions, index, action){
		
		var obj = action.cfg1;
		var enable = action.cfg11;
		var isEnabled = (enable == "YES") ? 'habilitado' : 'deshabilitado';
		
		_$errorMsg.append($('<p>').html('El objeto <i>' + obj + '</i> ha sido ' + isEnabled));
		
		//siguiente acción
		playAction(leaf, seqActions, ++index)  
	}
	
	
	//DDLaunchScene
	function playDDLaunchScene(leaf, seqActions, index, action){
		
		var scene = action.cfg1;
		
		_$errorMsg.append($('<p>').html('Transición a la escena <i>' + scene + '</i>'));
		
		//NO se ejecuta la siguiente acción porque se cambia de escena
		//siguiente acción
		//playAction(leaf, seqActions, ++index);
	}
	
	//DDLaunchNextScene
	function playDDLaunchNextScene(leaf, seqActions, index){
		
		_$errorMsg.append($('<p>').html('Transición a la siguiente escena.'));
		
		//NO se ejecuta la siguiente acción porque se cambia de escena
		//siguiente acción
		//playAction(leaf, seqActions, ++index);
	}
	
	//DDLaunchPrevScene
	function playDDLaunchPrevScene(leaf, seqActions, index){
		
		_$errorMsg.append($('<p>').html('Transición a la escena anterior.'));
		
		//NO se ejecuta la siguiente acción porque se cambia de escena
		//siguiente acción
		//playAction(leaf, seqActions, ++index);
	}
	
	
	//DDEnableBGMusic
	function playDDEnableBGMusic(leaf, seqActions, index, action){
		
		var   enable = action.cfg1
			, isEnabled = (enable == "YES") ? 'activado' : 'desactivado'
			, fileName = $('#bg-music').val()
		;
		
		previewBackPush(leaf.imageObj, 'music');
		
		//Ya estaba cargado el audio previamente
		if (previewController.bookAudio.src){
			
			if (enable == "YES"){
				var src = previewController.bookAudio.src;
				
				//stop del audio anterior antes del play
				previewController.bookAudio.pause();
		        previewController.bookAudio = new Audio("filename");
		        previewController.bookAudio.src = src;
		        previewController.bookAudio.play();
			}
			else {
				previewController.bookAudio.pause();	
			}
			
			_$errorMsg.append($('<p>').html('El sonido de fondo ha sido ' + isEnabled));	
		}
		else {
			
			_$errorMsg.append($('<p>').addClass('error').html('No hay seleccionado ningún sonido de fondo.'));	
		}
		
		//siguiente acción
		playAction(leaf, seqActions, ++index);
	}
	
	
	//DDChangeBGMusic
	function playDDChangeBGMusic(leaf, seqActions, index, action){
		
		var music = action.cfg1;
		
		previewBackPush(leaf.imageObj, 'music');
		
		if (previewController.bookAudio.src){
			previewController.bookAudio.pause();
		}
		previewController.bookAudio = new Audio("filename");
        previewController.bookAudio.src = "/getFile?fileName=" + music;
        previewController.bookAudio.play();
		
		_$errorMsg.append($('<p>').html('El sonido de fondo ha sido modificado a <i>' + music + '</i>'));
		
		//siguiente acción
		playAction(leaf, seqActions, ++index);
	}
	
	
	//DDStopAction
	function playDDStopAction(leaf, seqActions, index, action){
		
		var obj = action.cfg1;
		var act = action.cfg11;
		
		_$errorMsg.append($('<p>').html('Detenida la acción <i>' + act + '</i> del objeto <i>' + obj + '</i>'));
		
		//siguiente acción
		playAction(leaf, seqActions, ++index);
	}
	
	//DDDropEnd
	function playDDDropEnd(leaf, seqActions, index){
		
		//siguiente acción
		playAction(leaf, seqActions, ++index);
	}
	
	
	//DDHighlight
	function playDDHighlight(leaf, seqActions, index){
		
		//siguiente acción
		playAction(leaf, seqActions, ++index);
	}
	
	
	//DDIndexChange
	function playDDIndexChange(leaf, seqActions, index, action){
		
		var iniPos = leaf.zindex || 0;
		var finalPos = action.cfg1;
		
		//Almacenar posición inicial
		previewBackPush(leaf.imageObj, 'zindex', iniPos);
		
		leaf.imageObj.set('zindex', finalPos);
    	mainCanvas.canvas._objects.sort(function(a,b){return a.zindex-b.zindex;});
		mainCanvas.canvas.renderAll();	
		
		//siguiente acción
		playAction(leaf, seqActions, ++index);
	}
	
	
	//DDStateChange
	function playDDStateChange(leaf, seqActions, index, action){
		
		var state = action.cfg1;
		
		_$errorMsg.append($('<p>').html('El objeto <i>' + leaf.text + '</i> ha cambiado su estado a <i>' + state + '</i>'));
		
		//siguiente acción
		playAction(leaf, seqActions, ++index);
	}
	
	
	//DDPlayCustomAction
	function playDDPlayCustomAction(leaf, seqActions, index, action){
		
		var metodo = action.cfg1;
		
		_$errorMsg.append($('<p>').html('El objeto <i>' + leaf.text + '</i> ejectuta el método <i>' + metodo + '</i>'));
		
		//siguiente acción
		playAction(leaf, seqActions, ++index);
	}
	
	
	//DDDelay
	function playDDDelay(leaf, seqActions, index, action, duration){
		
		var d = duration || 0.01;
		
		window.setTimeout(function(){
			playAction(leaf, seqActions, ++index);	
		}, d);
	}
	
	
	//Reseteo de los objetos de la escena a su posición inicial
	function _restart(callback){
		
		_$errorMsg.html('');

		loopEnd = true;	
		
		//reset book audio
		if (previewController.bookAudio.src){
			previewController.bookAudio.pause();
		}
		previewController.bookAudio.src = previewController.initAudio
		
		//stop action audios
		var aa = previewController.actionAudios;
		while (aa.length){
			var curr = aa.pop();
			curr.pause();
			delete curr;
		}
		
		window.setTimeout(function(){
			
			
			$.each(_previewBack, function(i,e){
				
				//reestablecer src de la imagen modificada en la acción DDTextureChange
				if (e.mov == "src"){
					$(e.object.getElement()).load(function(){
						mainCanvas.canvas.renderAll();
					});
					e.object.getElement().setAttribute('src',  e.param);
					mainCanvas.canvas.renderAll();
				}
				
				//reset de las acciones ejecutadas
				else if (e.mov == "zindex"){
					
					e.object.set(e.mov, e.param);
					mainCanvas.canvas._objects.sort(function(a,b){return a.zindex-b.zindex;});
					mainCanvas.canvas.renderAll();	
				}
				
				//reset music
				else if (e.mov == "music"){
					//ya reseteado
				}
				else {
					
					e.object.set(e.mov, e.param).setCoords();
					mainCanvas.canvas.renderAll();
				}
			});
			
			mainCanvas.canvas.renderAll();
			
			loopDuration = 0;
			//loopEnd = false;
			_previewBack = [];
			_$restartPreview.addClass('off');
			//_$exitBtn.removeClass('off');
			
			
			if (typeof callback === "function"){
				callback();
			}
			
		}, loopDuration)

	}
	
	//Almacenar los movimientos ejecutados
	function previewBackPush(p_object, p_mov, p_param, p_remove){
		
		var push = true;
		var l = _previewBack.length;
		var i = 0;
		
		if (l){
			while (push && i < l){
				
				//no se almacena un movimiento si el objeto ya ha realizado el mismo movimiento anteriormente 
				if (_previewBack[i].object == p_object && _previewBack[i].mov == p_mov){
					push = false;
				}
				++i;
			}	
		}
		if (push){
			_previewBack.push({object: p_object, mov: p_mov, param:p_param, remove:p_remove})
		}
	}
	
	
	this.init = _init;
	this.open = _open;
}